----------------Platoon----------------
A 4am crack                  2016-09-25
---------------------------------------

Name: Platoon
Genre: arcade
Year: 1988
Credits:
  programming by L. Feddersen
  artwork by W. Holland, S. Chastain,
  A. Caberto, N. Nakamoto
Publisher: Data East
Platform: Apple //e or later (128K)
Media: double-sided 5.25-inch floppy
OS: Quick-DOS
Previous cracks: one uncredited crack

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy reboots endlessly

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing suspicious
  nothing on track $23
  hi-res disk scan shows no usage of
    half-tracks or quarter-tracks

Disk Fixer
  custom bootloader on track $00
  no sign of DOS, ProDOS, or Pascal
    operating system or disk catalog
  T00,S04 has "Quick-DOS(TM) Apple
    Program Loader" message, which is
    also displayed during boot

Why didn't any of my copies work?
  Probably a runtime protection check
  in early boot. Disks do not simply
  reboot unless someone tells them to.

Next steps:

  1. Search for the runtime protection
     check (the "easy" way)
  2. If that fails, trace the boot
     until I find it (the "hard" way)
  3. If that fails, burn everything

                   ~

               Chapter 1
         In Which We Get Lucky


One thing that all protection checks
have in common is they need to access
the disk drive. Since most protection
checks exploit edge cases of how bits
are stored on disk, they need to use
the lowest level access methods to
manipulate those bits manually.

The lowest level way to "read" a disk
is the data latch softswitch address in
the $C0xx range. For slot 6, it's
$C0EC, but to allow disks to boot from
any slot, developers usually use code
like this:

  LDX <slot number x 16>
  LDA $C08C,X

There's nothing that says you have to
use the X-register as the index or the
accumulator as the load register. But
most disks do, out of convention I
suppose (or fear of messing up such
low-level code in subtle ways).

Also, since developers don't actually
want people finding their protection-
related code, they may try to encrypt
it or obfuscate it on disk, in memory,
or both. But eventually, the code must
exist and the code must run, and it
must run on my machine, and I have the
final say on what my machine does or
does not do.

But sometimes you get lucky.

Turning to my trusty Disk Fixer sector
editor, I search the non-working copy
for "BD 8C C0", which is the opcode
sequence for "LDA $C08C,X".

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      ["BD 8C C0"]

                 --v--

------------- DISK SEARCH -------------

$00/$01-$1B   $00/$02-$D2   $00/$02-$DB
$00/$02-$E5   $00/$03-$03   $00/$07-$27
$00/$07-$37   $00/$07-$3C   $00/$0E-$3D


             PRESS [RETURN]

                 --^--

The match on sector $01 is part of a
series of softswitches that initialize
the drive; there is no other disk-
related code nearby. The matches on
sectors $02, $03, and $07 are part of
the legitimate RWTS -- tight loops that
wait for full nibbles coming out of the
data latch, then matching sequences of
"D5 AA 96" and "D5 AA AD" for the
address and data prologues.

But the match on sector $0E looks...
suspicious.

----------- DISASSEMBLY MODE ----------
0000:EA             NOP
0001:EA             NOP

; hard-code slot 6 -- very common for
; protection schemes (even if the real
; RWTS on the disk supports booting
; from any slot)
0002:A2 60          LDX   #$60

; some sort of Death Counter
0004:A9 56          LDA   #$56
0006:85 FD          STA   $FD
0008:A9 08          LDA   #$08
000A:C6 FC          DEC   $FC
000C:D0 04          BNE   $0012
000E:C6 FD          DEC   $FD

; If the Death Counter hits zero, that
; would be bad. Which part of "Death
; Counter" sounded good to you, anyway?
0010:F0 38          BEQ   $004A

; look for an #$FB nibble
0012:BC 8C C0       LDY   $C08C,X
0015:10 FB          BPL   $0012
0017:C0 FB          CPY   #$FB
0019:D0 ED          BNE   $0008

; kill a few cycles (not pointless,
; because the disk spins independently
; of the CPU, so all of these low-level
; disk reads are highly time-sensitive)
001B:F0 00          BEQ   $001D
001D:EA             NOP
001E:EA             NOP

; read data latch (note: no BPL loop
; here, we're just reading it once)
001F:BC 8C C0       LDY   $C08C,X

; do a compare to set or clear the
; carry bit (among other things, but
; it's the carry bit we care about)
0022:C0 08          CPY   #$08

; rotate the carry into the low bit of
; the accumulator
0024:2A             ROL

; if we just rolled a "1" bit out of
; the high bit of the accumulator, take
; this branch
0025:B0 0B          BCS   $0032

; next nibble needs to be #$FF
0027:BC 8C C0       LDY   $C08C,X
002A:10 FB          BPL   $0027
002C:C0 FF          CPY   #$FF

; ...otherwise we start over
002E:D0 D8          BNE   $0008

; loop back to get next nibble (this is
; an unconditional branch)
0030:F0 EB          BEQ   $001D

; execution continues here (from the
; "BCS" instruction 6 lines up) --
; get another nibble
0032:BC 8C C0       LDY   $C08C,X
0035:10 FB          BPL   $0032

; stash it in zero page
0037:84 FC          STY   $FC

; if the accumulator is anything but
; the bit pattern %00001010, start over
0039:C9 0A          CMP   #$0A
003B:D0 CB          BNE   $0008

; get one more nibble
003D:BD 8C C0       LDA   $C08C,X
0040:10 FB          BPL   $003D

; more bit twiddling (apparently this
; and the previous nibble combine to
; form a single value, 4-4-encoded)
0042:38             SEC
0043:2A             ROL
0044:25 FC          AND   $FC

; that 4-4-encoded value should be #$FF
0046:49 FF          EOR   #$FF
0048:F0 03          BEQ   $004D

; otherwise we reboot slot 6
004A:4C 00 C6       JMP   $C600

I got lost several times trying to
follow this routine. I think the
easiest way to explain it is to show
the difference between the original
disk and my non-working copy.

                   ~

               Chapter 2
        In Which We Get Visual


Here is the original disk, as seen by
the Copy II+ nibble editor. Nibbles
with extra "0" bits (timing bits) after
them are displayed in inverse on an
original machine, marked here with a
"+" after the nibble.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1BD4  LENGTH: 1814

1D28: DE BD 96 96 96 96 96 96   VIEW
1D30: 96 96 96 96 96 96 96 96
1D38: 96 96 96 96 96 96 96 EA
1D40: B6 FF DE AA EB FB BF FD
1D48: BB FB+FF FF+FF FF+FF+FF+
1D50: FF+D5 AA 96 FF FE AA AA
1D58: AA AB FF FF DE AA EB FF+
1D60: FF+FF+FF+FF+FF+D5 AA AD
1D68: A7 E9 DD 96 B6 F4 DC 9D

---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

It's easy to understand why a simple
sector copy failed. The sequence that
this code is looking for starts at
offset $1D49, which is between the end
of one sector and the beginning of the
next. (The data epilogue is at $1D42;
the next address prologue is at $1D51.)
Sector copiers discard everything
between those delimiters and rebuild
the track with a default pattern of
sync bytes. That pattern doesn't
include an $FB nibble, so the nibble
check fails.

But the EDD bit copy also failed. Here
is the original disk's pattern at
offset $1D49:

  - #$FB + timing bit
  - #$FF
  - #$FF + timing bit
  - #$FF
  - #$FF + timing bit

And here is what the same part of the
track looks like on my failed EDD copy:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1BD4  LENGTH: 1814

1D28: DE BD 96 96 96 96 96 96   VIEW
1D30: 96 96 96 96 96 96 96 96
1D38: 96 96 96 96 96 96 96 EA
1D40: B6 FF DE AA EB FB BF FD
1D48: BB FB+FF FF FF+FF+FF+FF+
1D50: FF+D5 AA 96 FF FE AA AA
1D58: AA AB FF FF DE AA EB FF+
1D60: FF+FF+FF+FF+FF+D5 AA AD
1D68: A7 E9 DD 96 B6 F4 DC 9D

---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

A subtle difference! The sequence at
offset $1D49 now looks like this:

  - #$FB + timing bit
  - #$FF
  - #$FF
  - #$FF + timing bit
  - #$FF + timing bit

This code is looking for #$FF nibbles
with an alternating pattern of timing
bit, no timing bit, timing bit, no
timing bit. It doesn't find that on the
bit copy, so it knows it's not running
on an original disk.

Returning to the code listing, we will
branch to offset $004D if the check
succeeds...

; set up zero page
004D:A9 60          LDA   #$60
004F:85 2B          STA   $2B
0051:A9 09          LDA   #$09
0053:85 27          STA   $27

; restore code from T00,S00 that called
; this routine
0055:A9 01          LDA   #$01
0057:8D 00 08       STA   $0800
005A:A9 A5          LDA   #$A5
005C:8D 01 08       STA   $0801
005F:A9 27          LDA   #$27
0061:8D 02 08       STA   $0802
0064:A9 C9          LDA   #$C9
0066:8D 03 08       STA   $0803
0069:A9 09          LDA   #$09
006B:8D 04 08       STA   $0804

; and continue the boot as if nothing
; ever happened
006E:4C 01 08       JMP   $0801

Ah! Looking at the boot sector, I see
that it is designed to read 3 sectors
instead of the usual 1 -- into $0800,
$0900, and $0A00. So this protection
check in T00,S0E will be loaded into
$0A00, which is exactly where the boot
sector jumps to:

                 --v--

T00,S00
----------- DISASSEMBLY MODE ----------
0000:03
0001:4C 00 0A       JMP   $0A00

                 --^--

So this entire check is designed to be
transparent and (almost) bootloader-
independent. It has no side effects;
the bootloader doesn't care that the
protection check succeeded. In fact, it
doesn't even realize it ran!

                   ~

               Chapter 3
  In Which We Think About Automation


Since the release of Passport
https://archive.org/details/Passport4am
I've changed the way I think about
runtime protection checks like this.
I used to think "what's the easiest way
to defeat the code on this disk?" Now I
think "what does the code on this disk
have in common with code I've seen on
other disks, and could I detect and
defeat it automatically?"

In this case, the easiest way to defeat
the code on this disk is to bypass the
check altogether and jump directly to
the success path at $0A4D.

However, I've seen this protection
check on many other disks. What do they
have in common?

According to my records, the last two
disks I cracked with this same check
were
  #843 Zork Quest
  #684 Math Shop v1986-10-27

Some notable differences:

- Bootloader/OS. Math Shop was ProDOS-
  based. Zork Quest was custom. This is
  Quick-DOS (although really it's more
  like a patch on top of Quick-DOS, not
  integrated into it).

- When the protection check executes.
  This disk does it before even loading
  the RWTS from track 0. Math Shop did
  it as part of a .SYSTEM file (after
  all of ProDOS was loaded). Zork Quest
  loaded several tracks before calling.

- Where the code starts within the
  sector. This code starts at offset
  $02. But on Math Shop, the code
  started at offset $9A. On Zork Quest,
  it was at offset $00. Note there are
  no absolute jumps within the code;
  it's all relocatable. That makes it
  slightly more difficult to find, but
  it's not insurmountable.

- How the code is called. Sometimes the
  check is a self-contained subroutine
  where I could just put an "RTS" at
  the very beginning of it to bypass
  the check. But on this disk, there is
  more code that needs to execute after
  the check succeeds -- otherwise the
  disk will not boot!

Similarities:

- The actual magic bit sequence was on
  track 0 on all three disks. But that
  is not guaranteed; the caller could
  put the drive head on any track
  before calling the protection check.

- Much of the code is similar, but it's
  also maddeningly different. To wit:

; hard-coded slot (also used by Zork
; Quest, but Math Shop used ProDOS
; globals to determine the boot slot)
0002:A2 60          LDX   #$60

; varies (other disks use other zero
; page addresses)
0004:A9 56          LDA   #$56
0006:85 FD          STA   $FD
0008:A9 08          LDA   #$08
000A:C6 FC          DEC   $FC
000C:D0 04          BNE   $0012
000E:C6 FD          DEC   $FD

; varies (Math Shop / Zork Quest both
; use a branch length of #$3C because
; they have more code before the
; failure path)
0010:F0 38          BEQ   $004A

; identical (including branch lengths)
0012:BC 8C C0       LDY   $C08C,X
0015:10 FB          BPL   $0012
0017:C0 FB          CPY   #$FB
0019:D0 ED          BNE   $0008
001B:F0 00          BEQ   $001D
001D:EA             NOP
001E:EA             NOP
001F:BC 8C C0       LDY   $C08C,X
0022:C0 08          CPY   #$08
0024:2A             ROL
0025:B0 0B          BCS   $0032
0027:BC 8C C0       LDY   $C08C,X
002A:10 FB          BPL   $0027
002C:C0 FF          CPY   #$FF
002E:D0 D8          BNE   $0008
0030:F0 EB          BEQ   $001D
0032:BC 8C C0       LDY   $C08C,X
0035:10 FB          BPL   $0032

; varies (different zero page address)
0037:84 FC          STY   $FC

; identical (including branch lengths)
0039:C9 0A          CMP   #$0A
003B:D0 CB          BNE   $0008
003D:BD 8C C0       LDA   $C08C,X
0040:10 FB          BPL   $003D
0042:38             SEC
0043:2A             ROL

; varies (differnet zero page address)
0044:25 FC          AND   $FC

; identical
0046:49 FF          EOR   #$FF

; varies maddeningly (Zork Quest has a
; "BNE" branch to the failure path, but
; Math Shop has a "BEQ" branch to the
; failure path -- the exact opposite
; logic! They expect different 4-4-
; encoded values after the magic bit
; sequence!)
0048:F0 03          BEQ   $004D

; This is obviously a failure path, but
; other disks have different failure
; behavior.
004A:4C 00 C6       JMP   $C600

I'm still not sure I could automate the
detection and proper patching of this
protection check, in the general case.
I can compensate for the different zero
page addresses, and even the different
branch lengths. But this disk demands a
4-4-encoded value of #$FF immediately
after the magic bit sequence, and Math
Shop demands any value except #$FF --
the exact opposite condition!

To decide what the "correct" 4-4-
encoded value is, I would need to
examine a *lot* more samples. On this
disk, the combination of "BEQ +3 / JMP
$C600" makes it pretty clear that the
correct value is #$FF. But Zork Quest
didn't have the "JMP $C600", and Math
Shop had a "BEQ" to the failure path!

In the meantime, I have a working patch
for this disk: jump to the success path
instead of the start of the protection
check.

T00,S00,$02: 00 -> 4D

]PR#6
...works...

To be continued...

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 844
------------------EOF------------------
